#!/bin/bash

# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

. "$(dirname "$0")/common.sh" || exit 1

# Script must run inside the chroot
restart_in_chroot_if_needed "$@"

assert_not_root_user

# Developer-visible flags.
DEFINE_string board "${DEFAULT_BOARD}" \
  "The board to build packages for."
DEFINE_boolean usepkg "${FLAGS_TRUE}" \
  "Use binary packages to bootstrap when possible."
DEFINE_boolean usepkgonly "${FLAGS_FALSE}" \
  "Only use binary packages to bootstrap; abort if any are missing."
DEFINE_boolean noworkon "${FLAGS_FALSE}" \
  "Don't force-build workon packages."
DEFINE_boolean showoutput "${FLAGS_FALSE}" \
  "Show all output from parallel_emerge."
DEFINE_boolean withautotest "${FLAGS_TRUE}" \
  "Build autotest client code."
DEFINE_boolean fetchonly "${FLAGS_FALSE}" \
  "Don't build anything, instead only fetch what is needed."
DEFINE_boolean unpackonly "${FLAGS_FALSE}" \
  "Don't build anything; instead only fetch and unpack what is needed."
DEFINE_boolean withdebugsymbols "${FLAGS_FALSE}" \
  "Install the debug symbols for all packages"

# The --board_root flag specifies the environment variables ROOT and PKGDIR.
# This allows fetching and emerging of all packages to specified board_root.
# Note that --board_root will setup the board normally in /build/$BOARD, if it's
# not setup yet. It also expects the toolchain to already be installed in the
# board_root. --usepkgonly and --norebuild are required, because building is not
# supported when board_root is set.
# enforce this)."
DEFINE_string board_root "" \
  "Emerge packages to board_root."

FLAGS_HELP="usage: $(basename $0) [flags] [packages]

build_packages updates the set of binary packages needed by Chrome OS. It will
cross compile all packages that have been updated into the given target's root
and build binary packages as a side-effect. The output packages will be picked
up by the build_image script to put together a bootable Chrome OS image.

If [packages] are specified, only build those specific packages (and any
dependencies they might need).

For the fastest builds, use --nowithautotest --noworkon.
"
show_help_if_requested "$@"

# The following options are advanced options, only available to those willing
# to read the source code. They are not shown in help output, since they are
# not needed for the typical developer workflow.
DEFINE_string accept_licenses "" \
  "Licenses to append to the accept list."
DEFINE_integer jobs -1 \
  "How many packages to build in parallel at maximum."
DEFINE_boolean norebuild "${FLAGS_FALSE}" \
  "Don't automatically rebuild dependencies."
DEFINE_boolean skip_chroot_upgrade "${FLAGS_FALSE}" \
  "Don't run the chroot upgrade automatically; use with care."
DEFINE_boolean skip_toolchain_update "${FLAGS_FALSE}" \
  "Don't update toolchain automatically."
DEFINE_boolean withdev "${FLAGS_TRUE}" \
  "Build useful developer friendly utilities."
DEFINE_boolean withdebug "${FLAGS_TRUE}" \
  "Build debug versions of Chromium-OS-specific packages."
DEFINE_boolean withfactory "${FLAGS_TRUE}" \
  "Build factory installer."
DEFINE_boolean withtest "${FLAGS_TRUE}" \
  "Build packages required for testing."
DEFINE_boolean chrome_binhost_only $FLAGS_FALSE \
  "Only fetch packages from the Chrome binhost."

# The --reuse_pkgs_from_local_boards flag tells Portage to share binary
# packages between boards that are built locally, so that the total time
# required to build several boards is reduced. This flag is only useful
# when you are not able to use remote binary packages, since remote binary
# packages are usually more up to date than anything you have locally.
DEFINE_boolean reuse_pkgs_from_local_boards $FLAGS_FALSE \
  "Bootstrap from local packages instead of remote packages."

# Parse command line
FLAGS "$@" || exit 1
eval set -- "${FLAGS_ARGV}"

# Die on any errors.
switch_to_strict_mode

# Right now build_packages has to be run from scripts/
. ${SRC_ROOT}/third_party/chromiumos-overlay/chromeos/config/chromeos_version.sh

if [[ -z "${FLAGS_board}" ]]; then
  echo "Error: --board is required."
  exit 1
fi

CHROMITE_BIN="${GCLIENT_ROOT}/chromite/bin"

# Before we can run any tools, we need to update chroot or setup_board.
UPDATE_ARGS=()
if [[ -n ${FLAGS_accept_licenses} ]]; then
  UPDATE_ARGS+=( --accept_licenses "${FLAGS_accept_licenses}" )
fi
if [ "${FLAGS_usepkg}" -eq "${FLAGS_TRUE}" ]; then
  UPDATE_ARGS+=( --usepkg )
else
  UPDATE_ARGS+=( --nousepkg )
fi
if [[ "${FLAGS_jobs}" -ne -1 ]]; then
  UPDATE_ARGS+=( --jobs=${FLAGS_jobs} )
fi
if [ "${FLAGS_chrome_binhost_only}" -eq "${FLAGS_TRUE}" ]; then
  UPDATE_ARGS+=( --chrome_binhost_only )
fi
if [ "${FLAGS_reuse_pkgs_from_local_boards}" -eq "${FLAGS_TRUE}" ]; then
  UPDATE_ARGS+=( --reuse_pkgs_from_local_boards )
fi
if [ "${FLAGS_skip_toolchain_update}" -eq "${FLAGS_TRUE}" ]; then
  UPDATE_ARGS+=( --skip_toolchain_update )
fi
if [ "${FLAGS_skip_chroot_upgrade}" -eq "${FLAGS_TRUE}" ]; then
  UPDATE_ARGS+=( --skip_chroot_upgrade )
fi
if [[ ${FLAGS_unpackonly} -eq ${FLAGS_TRUE} ]]; then
  UPDATE_ARGS+=( --skip_board_pkg_init )
fi
if [[ -n ${FLAGS_board_root} ]]; then
  UPDATE_ARGS+=( --board_root "${FLAGS_board_root}" )
fi

"${SCRIPTS_DIR}"/setup_board --quiet --board=${FLAGS_board} "${UPDATE_ARGS[@]}"

sudo_clear_shadow_locks "/build/${FLAGS_board}"

# Setup all the emerge command/flags.
EMERGE_FLAGS=( -uDNv --backtrack=30 --select --newrepo )

EMERGE_CMD=(
  "${CHROMITE_BIN}/parallel_emerge"
  --board=${FLAGS_board}
)

if [[ "${FLAGS_fetchonly}" -eq "${FLAGS_TRUE}" ]]; then
  EMERGE_CMD+=( --fetchonly )
fi
if [[ ${FLAGS_unpackonly} -eq ${FLAGS_TRUE} ]]; then
  EMERGE_CMD+=( --unpackonly )
fi

EMERGE_CMD+=( ${EXTRA_BOARD_FLAGS} )

if [[ "${FLAGS_usepkg}" -eq "${FLAGS_TRUE}" ||
      "${FLAGS_reuse_pkgs_from_local_boards}" -eq "${FLAGS_TRUE}" ||
      "${FLAGS_usepkgonly}" -eq "${FLAGS_TRUE}" ]]; then
  # Use binary packages. Include all build-time dependencies,
  # so as to avoid unnecessary differences between source
  # and binary builds.
  EMERGE_FLAGS+=( --getbinpkg --with-bdeps y )
  if [[ ${FLAGS_usepkgonly} -eq ${FLAGS_TRUE} ]]; then
    EMERGE_FLAGS+=( --usepkgonly )
  else
    EMERGE_FLAGS+=( --usepkg )
  fi
fi

if [[ "${FLAGS_jobs}" -ne -1 ]]; then
  EMERGE_FLAGS+=( --jobs=${FLAGS_jobs} )
fi

if [[ "${FLAGS_norebuild}" -eq "${FLAGS_FALSE}" ]]; then
  EMERGE_FLAGS+=( --rebuild-if-unbuilt )
fi
if [[ "${FLAGS_showoutput}" -eq "${FLAGS_TRUE}" ]]; then
  EMERGE_FLAGS+=( --show-output )
fi

if [[ "${FLAGS_withdebug}" -eq "${FLAGS_FALSE}" ]]; then
  export USE="${USE} -cros-debug"
fi

# Figure out which packages we should be building.
PACKAGES=( "$@" )
if [[ $# -eq 0 ]]; then
  PACKAGES=( virtual/target-os )
  if [[ "${FLAGS_withdev}" -eq "${FLAGS_TRUE}" ]]; then
    PACKAGES+=( virtual/target-os-dev )
  fi
  if [[ "${FLAGS_withfactory}" -eq "${FLAGS_TRUE}" ]]; then
    PACKAGES+=( chromeos-base/chromeos-installshim )
    PACKAGES+=( chromeos-base/chromeos-factory )
    PACKAGES+=( chromeos-base/chromeos-hwid )
    PACKAGES+=( chromeos-base/autotest-factory-install )
  fi
  if [[ "${FLAGS_withtest}" -eq "${FLAGS_TRUE}" ]]; then
    PACKAGES+=( virtual/target-os-test )
  fi
  if [[ "${FLAGS_withautotest}" -eq "${FLAGS_TRUE}" ]]; then
    PACKAGES+=( chromeos-base/autotest-all )
  fi
fi

# Verify that all packages can be emerged from scratch, without any
# backtracking. Only print the output if this step fails.
info "Checking package dependencies are correct: ${PACKAGES[*]}"
if ! OUTPUT=$(emerge-${FLAGS_board} -pe --backtrack=0 \
              "${PACKAGES[@]}" 2>&1); then
  printf "%s\n" "${OUTPUT}"
  die_notrace "emerge detected broken ebuilds. See error message above."
fi

# Build cros_workon packages when they are changed.
CROS_WORKON_PKGS=()
if [ "${FLAGS_noworkon}" -eq "${FLAGS_FALSE}" ]; then
  LIST_MODIFIED_PACKAGES="${CHROMITE_BIN}/cros_list_modified_packages"
  CROS_WORKON_PKGS+=( $("${LIST_MODIFIED_PACKAGES}" --board=${FLAGS_board}) )

  # TODO(anush): Make chrome a fake cros-workon package.
  if [[ -n "${CHROME_ORIGIN}" ]]; then
    CROS_WORKON_PKGS+=( chromeos-base/chromeos-chrome )
  fi
fi

if [[ ${#CROS_WORKON_PKGS[@]} -gt 0 ]]; then
  EMERGE_FLAGS+=(
    --reinstall-atoms="${CROS_WORKON_PKGS[*]}"
    --usepkg-exclude="${CROS_WORKON_PKGS[*]}"
  )
fi

# Prepare tmp file to capture emerge output from tee.
tmpfile=$(mktemp -t tmp.build_packages-emerge.XXXXXX)
trap "rm -f '${tmpfile}'" EXIT

if [[ -n "${FLAGS_board_root}" ]]; then
  export ROOT="${FLAGS_board_root}"
  export PORTAGE_CONFIGROOT="${ROOT}"
  export SYSROOT="${ROOT}"
  export PKGDIR="${ROOT}"/packages
fi

info "Merging board packages now"
(
  set -o pipefail
  sudo -E "${EMERGE_CMD[@]}" "${EMERGE_FLAGS[@]}" "${PACKAGES[@]}" | \
    tee "${tmpfile}"
)

# Extract total package count from emerge output.
package_count=$(awk '$0 ~ /^Total: [0-9]+ packages/ { print $2 }' "${tmpfile}")
rm "${tmpfile}"
trap - EXIT

echo "Builds complete"

if [[ ${FLAGS_withdebugsymbols} -eq ${FLAGS_TRUE} ]]; then
  info "fetching the debug symbols"
  sudo -E "${GCLIENT_ROOT}/chromite/bin/cros_install_debug_syms" \
    "--board=${FLAGS_board}" "--all"
fi

EXTRA_COMMAND_STATS[package_count]=${package_count}
command_completed
echo "Done"
